<!--- 
@%HEADER%@


Copyright @%CUR_YEAR%@ Seb Duggan

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--->

<cfcomponent extends="BasePlugin">

<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->	
	<cffunction name="init" access="public" output="false" returntype="any">
		<cfargument name="mainManager" type="any" required="true" />
		<cfargument name="preferences" type="any" required="true" />
		
		<cfset setManager(arguments.mainManager) />
		<cfset setPreferencesManager(arguments.preferences) />
		<cfset setPackage("com/sebduggan/mango/plugins/lastfm") />
		
		<!--- Default preferences --->
		<cfsavecontent variable="VARIABLES.defaultCss"><cfinclude template="admin/default.css" /></cfsavecontent>
		<cfset initSettings(
				userName = "LASTFM_USERNAME",
				apiKey = "LASTFM_API_KEY",
				title = "What I&rsquo;m listening to",
				count = "4",
				logo = "red",
				cacheMins = "3",
				css = VARIABLES.defaultCss,
				showAlbum = "0",
				showCover = "1",
				showTime = "1",
				coverSize = "42"
			) />
		
		<!--- Set up feed caching --->
		<cfset resetCache() />
			
		<cfreturn this/>
	</cffunction>

<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->	
	<cffunction name="setup" hint="This is run when a plugin is activated" access="public" output="false" returntype="any">
		<cfreturn "Last.fm plugin activated. Would you like to <a href='generic_settings.cfm?event=showLastfmSettings&amp;owner=lastfm&amp;selected=showLastfmSettings'>configure it now</a>?" />
	</cffunction>
	
	<cffunction name="unsetup" hint="This is run when a plugin is de-activated" access="public" output="false" returntype="any">
		<cfreturn />
	</cffunction>

<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->	
	<cffunction name="handleEvent" hint="Asynchronous event handling" access="public" output="false" returntype="any">
		<cfargument name="event" type="any" required="true" />		
		<cfreturn />
	</cffunction>

<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->	
	<cffunction name="processEvent" hint="Synchronous event handling" access="public" output="false" returntype="any">
		<cfargument name="event" type="any" required="true" />

			<cfset var LOCAL = StructNew() />
			
			<cfswitch expression="#ARGUMENTS.event.name#">
				
				
				<cfcase value="beforeHtmlHeadEnd">
					<cfset LOCAL.css = getCssOutput() />
					<cfset LOCAL.outputData = arguments.event.getOutputData() />
					<cfset arguments.event.setOutputData(LOCAL.outputData & LOCAL.css) />
				</cfcase>
				
			
				<cfcase value="getPods">
					<!--- Should we use data from the cache? --->
					<cfif Now() gt VARIABLES.cache.expires>
						<!--- No - cache data has expired --->
						<!--- cftry - any errors here will leave the cache untouched, and display cached data --->
						<cftry>
							<!--- Get the feed from Last.fm --->
							<cfhttp url="http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=#getSetting("userName")#&api_key=#getSetting("apiKey")#&limit=#getSetting("count")#" timeout="5" result="LOCAL.feed" />
							
							<!--- Parse the XML feed --->
							<cfset LOCAL.xml = XmlParse(LOCAL.feed.fileContent) /> 
							
							<!--- Check we've got a successful response --->
							<cfif LOCAL.xml["lfm"].xmlAttributes["status"] is "ok">
								<!--- Cache the track data, and set the new cache expiry date/time --->
								<cfset VARIABLES.cache.data = makeTrackArray(LOCAL.xml) />
								<cfset VARIABLES.cache.added = Now() />
								<cfset VARIABLES.cache.expires = DateAdd("n", getSetting("cacheMins"), Now()) />
							</cfif>
							
							<cfcatch>
								<!--- There was an error, so don't try updating the cache for another few minutes --->
								<cfset VARIABLES.cache.expires = DateAdd("n", getSetting("cacheMins"), Now()) />
							</cfcatch>
						</cftry>	
					</cfif>
					
					
					<cfif ArrayLen(VARIABLES.cache.data)>
						<!--- There's something in the cache, so build the pod --->
						<cfset LOCAL.pod = StructNew() />
						<cfset LOCAL.pod.title = getSetting("title") />
						<cfset LOCAL.pod.content = getHtmlOutput() />
						<cfset LOCAL.pod.id = "lastfm" />
						
						<!--- Return the completed pod --->
						<cfset ARGUMENTS.event.addPod(LOCAL.pod) />
					</cfif>
					
				</cfcase>
				
				
				<!--- Pod reference for pod manager --->
				<cfcase value="getPodsList">
					<cfset LOCAL.pod = StructNew() />
					<cfset LOCAL.pod.title = "Last.fm" />
					<cfset LOCAL.pod.id = "lastfm" />
					<cfset ARGUMENTS.event.addPod(LOCAL.pod)>
				</cfcase>
					
					
				<!--- admin dashboard event --->
				<cfcase value="dashboardPod">
					<cfif getSetting("userName") IS "LASTFM_USERNAME" OR getSetting("apiKey") IS "LASTFM_API_KEY">
						<!--- add a pod warning about missing Last.fm info --->
					
						<cfsavecontent variable="LOCAL.outputData">
						<cfoutput><p class="warning">You have not set up the Last.fm plugin yet. Would you link to <a href="generic_settings.cfm?event=showLastfmSettings&amp;owner=lastfm&amp;selected=showLastfmSettings">configure it now?</a></p>
						</cfoutput>
						</cfsavecontent>
						
						<cfset LOCAL.data = structnew() />
						<cfset LOCAL.data.title = "Last.fm" />
						<cfset LOCAL.data.content = LOCAL.outputData />
						<cfset ARGUMENTS.event.addPod(LOCAL.data)>
					</cfif>
				</cfcase>
				
			
				<!--- admin nav event --->
				<cfcase value="settingsNav">
					<cfset LOCAL.link = structnew() />
					<cfset LOCAL.link.owner = "lastfm">
					<cfset LOCAL.link.page = "settings" />
					<cfset LOCAL.link.title = "Last.fm" />
					<cfset LOCAL.link.eventName = "showLastfmSettings" />
					
					<cfset ARGUMENTS.event.addLink(LOCAL.link)>
				</cfcase>
				
			
				<!--- admin event --->
				<cfcase value="showLastfmSettings">
					<cfset LOCAL.data = ARGUMENTS.event.getData() />
					
					<!--- Pre-fill the settings form with the current settings --->
					<cfset LOCAL.formFieldData = VARIABLES.settings />
						
					<!--- Process if form has been submitted --->			
					<cfif structkeyexists(LOCAL.data.externaldata,"apply")>
						<!--- Clear the cache? --->
						<cfif StructKeyExists(LOCAL.data.externaldata,"clearCache") and LOCAL.data.externaldata.clearCache eq 1>
							<cfset resetCache() />
						</cfif>
						
						<!--- Validate the form submission --->
						<cfparam name="LOCAL.data.externaldata.showAlbum" default="0" />
						<cfparam name="LOCAL.data.externaldata.showCover" default="0" />
						<cfparam name="LOCAL.data.externaldata.showTime" default="0" />
						<cfset LOCAL.validate = validate(argumentCollection=LOCAL.data.externaldata) />
						<cfif LOCAL.validate.success>
							<!--- Validation was successful --->
							<!--- Update component with new values --->
							<cfif StructKeyExists(LOCAL.data.externaldata,"defaultCss")>
								<cfset LOCAL.data.externaldata.css = VARIABLES.defaultCss />
							</cfif>
							<cfset setSettings(
									userName = LOCAL.data.externaldata.userName,
									apiKey = LOCAL.data.externaldata.apiKey,
									title = LOCAL.data.externaldata.title,
									count = LOCAL.data.externaldata.count,
									logo = LOCAL.data.externaldata.logo,
									cacheMins = LOCAL.data.externaldata.cacheMins,
									css = LOCAL.data.externaldata.css,
									showAlbum = LOCAL.data.externaldata.showAlbum,
									showCover = LOCAL.data.externaldata.showCover,
									showTime = LOCAL.data.externaldata.showTime,
									coverSize = LOCAL.data.externaldata.coverSize
								) />
							
							<!--- Save the new values to the settings file --->
							<cfset persistSettings() />
							
							<!--- Set message to display on screen --->
							<cfset LOCAL.data.message.setstatus("success") />
							<cfset LOCAL.data.message.setType("settings") />
							<cfset LOCAL.data.message.settext("Last.fm info updated")/>
						<cfelse>
							<!--- Validation failed --->
							<!--- Display appropriate messages --->
							<cfset LOCAL.data.message.setstatus("error") />
							<cfset LOCAL.data.message.setType("settings") />
							<cfset LOCAL.data.message.settext(ArrayToList(LOCAL.validate.errors,"<br />"))/>
							<!--- Use the submitted values to pre-fill the settings form --->
							<cfset LOCAL.formFieldData = LOCAL.data.externaldata />
						</cfif>
					</cfif>
					
					<!--- Include the settings form --->
					<cfsavecontent variable="LOCAL.page">
						<cfinclude template="admin/settingsForm.cfm">
					</cfsavecontent>
						
					<!--- change message --->
					<cfset LOCAL.data.message.setTitle("Last.fm settings") />
					<cfset LOCAL.data.message.setData(LOCAL.page) />
				</cfcase>
				
			</cfswitch>
		
		<cfreturn ARGUMENTS.event />
	</cffunction>
	
	<cffunction name="validate" output="false" returntype="struct" access="private" hint="Validate the form input values">
		<cfset var validate = StructNew() />
			<cfset validate.errors = ArrayNew(1) />
			<cfset validate.errorFields = ArrayNew(1) />
			<cfset validate.message = "" />
		
		
		<!--- Validation rules go here! --->
		<cfif not StructKeyExists(ARGUMENTS,"userName") or not Len(ARGUMENTS.userName) or ARGUMENTS.userName is "LASTFM_USERNAME">
			<cfset ArrayAppend(validate.errors,"Please enter your Last.fm user name") />
			<cfset ArrayAppend(validate.errorFields,"userName") />
		</cfif>
		<cfif not StructKeyExists(ARGUMENTS,"apiKey") or not Len(ARGUMENTS.apiKey) or ARGUMENTS.userName is "LASTFM_API_KEY">
			<cfset ArrayAppend(validate.errors,"Please enter your Last.fm API key") />
			<cfset ArrayAppend(validate.errorFields,"apiKey") />
		</cfif>
		<cfif not StructKeyExists(ARGUMENTS,"count") or not isValid("integer",ARGUMENTS.count)>
			<cfset ArrayAppend(validate.errors,"Please enter a whole number for the number of tracks") />
			<cfset ArrayAppend(validate.errorFields,"count") />
		</cfif>
		<cfif not StructKeyExists(ARGUMENTS,"coverSize") or not isValid("integer",ARGUMENTS.coverSize)>
			<cfset ArrayAppend(validate.errors,"Please enter a whole number for the album artwork size") />
			<cfset ArrayAppend(validate.errorFields,"coverSize") />
		</cfif>
		<cfif not StructKeyExists(ARGUMENTS,"cacheMins") or not isValid("integer",ARGUMENTS.cacheMins)>
			<cfset ArrayAppend(validate.errors,"Please enter a whole number for the cache duration") />
			<cfset ArrayAppend(validate.errorFields,"cacheMins") />
		</cfif>
		<!--- End of validation rules --->
		
		
		<cfif ArrayLen(validate.errors)>
			<!--- Validation failed --->
			<cfset validate.success = false />
		<cfelse>
			<!--- Validation was successful --->
			<cfset validate.success = true />
		</cfif>
		
		<cfreturn validate />
	</cffunction>
	
	<cffunction name="resetCache" output="false" returntype="void" access="private" hint="Clear all data from the track cache">
		<cfset VARIABLES.cache = StructNew() />
			<cfset VARIABLES.cache.data = ArrayNew(1) />
			<cfset VARIABLES.cache.added = "" />
			<cfset VARIABLES.cache.expires = DateAdd("n", -1, Now()) />
	</cffunction>
		
	<cffunction name="niceTime" output="false" returntype="string" access="private" hint="Format the date to a friendly string">
		<cfargument name="date" type="date" required="true" />
		
		<cfset var result = "" />
		<!--- Convert the date to the web server's time zone --->
		<cfset var d = DateConvert("utc2Local",ARGUMENTS.date) />
				
		<cfif DateDiff("n",d,Now()) LTE 5>
			<!--- In the last 5 minutes --->
			<cfset result = "Just listened" />
		<cfelseif DateDiff("n",d,Now()) LTE 59>
			<!--- Within the last hour --->
			<cfset result = "#DateDiff("n",d,Now())# minutes ago" />
		<cfelseif DateDiff("h",d,Now()) EQ 1>
			<!--- Less than 2 hours ago --->
			<cfset result = "1 hour ago" />
		<cfelseif DateDiff("h",d,Now()) LTE 24>
			<!--- With the last 24 hours --->
			<cfset result = "#DateDiff("h",d,Now())# hours ago" />
		<cfelseif Fix(Now()) - Fix(d) EQ 1>
			<!--- Yesterday --->
			<cfset result = "Yesterday" />
		<cfelse>
			<!--- How many days ago? --->
			<cfset result = "#DateDiff("d",d,Now())# days ago" />
		</cfif>
		
		<cfreturn result />
	</cffunction>

	<cffunction name="makeTrackArray" output="false" returntype="array" access="private" hint="Make an array of tracks from the supplied XML">
		<cfargument name="xml" type="XML" required="true" />
		
		<cfset var trackArray = ArrayNew(1) />
		<cfset var tracks = "" />
		<cfset var track = "" />
		<cfset var tempdate = "" />
		<cfset var i = 0 />
		
		<!--- Get an array of tracks from the XML --->
		<cfset tracks = XmlSearch(ARGUMENTS.xml,"/lfm/recenttracks/track") />

		<!--- Loop over the tracks in the feed --->
		<cfloop from="1" to="#ArrayLen(tracks)#" index="i">
			<!--- Each track will be a struct --->
			<cfset track = StructNew() />
				<!--- Set the track data values --->
				<cfset track.nowplaying = StructKeyExists(tracks[i].xmlAttributes,"nowplaying") and tracks[i].xmlAttributes["nowplaying"] is "true" />
				<cfset track.name = tracks[i]["name"].xmlText />
				<cfset track.artist = tracks[i]["artist"].xmlText />
				<cfset track.album = tracks[i]["album"].xmlText />
				<cfset track.url = tracks[i]["url"].xmlText />
				<cfset track.image = tracks[i]["image"][2].xmlText />
				<!--- We parse the date string, as the last.fm UTC value can't be trusted with Daylight Saving Time --->
				<cfset tempdate = tracks[i]["date"].xmlText />
				<cfset track.date = ParseDateTime("#ListGetAt(tempdate,2," ")# #ListGetAt(tempdate,1," ")#, #ListGetAt(tempdate,3,", ")# #ListLast(tempdate," ")#") />
				
			<!--- Append the track struct to the array --->
			<cfset ArrayAppend(trackArray,track) />
		</cfloop>
	
		<cfreturn trackArray />
	</cffunction>

	<cffunction name="getHtmlOutput" output="false" returntype="string" access="private" hint="Generate the HTML list of tracks">
		<cfset var html = ""/>
		<cfset var i = 0 />
		
		<cfsavecontent variable="html">
			<cfoutput>
			<!-- Last.fm data cached: #VARIABLES.cache.added# -->
			<ul id="lastfm-tracks">
			<cfloop from="1" to="#Min(ArrayLen(VARIABLES.cache.data),getSetting("count"))#" index="i">
				<li>
					<a href="#VARIABLES.cache.data[i].url#" class="lastfm-trackname"><cfif Len(VARIABLES.cache.data[i].image)><cfif getSetting("showCover")><img src="#VARIABLES.cache.data[i].image#" width="#getSetting("coverSize")#" class="lastfm-cover" alt="" /></cfif>
					</cfif>#VARIABLES.cache.data[i].name#</a><br />
					<span class="lastfm-artist">#VARIABLES.cache.data[i].artist#</span>
					<cfif getSetting("showAlbum") and Len(VARIABLES.cache.data[i].album)><br /><span class="lastfm-album">#VARIABLES.cache.data[i].album#</span></cfif>
					<cfif getSetting("showTime")><br /><span class="lastfm-time"><cfif VARIABLES.cache.data[i].nowplaying><img src="#getAssetPath()#icon_eq.gif" width="12" height="12" alt="" /> Listening now<cfelse>#niceTime(VARIABLES.cache.data[i].date)#</cfif></span></cfif>
				</li>
			</cfloop>
			</ul>
			<p id="lastfm-poweredby">Powered by <a href="http://www.last.fm"><img src="#getAssetPath()#lastfm_#getSetting("logo")#.png" width="48" height="15" alt="Last.fm" /></a></p>
			</cfoutput>
		</cfsavecontent>
		
		<cfreturn html />
	</cffunction>
	
	<cffunction name="getCssOutput" output="false" returntype="string" access="private" hint="Generate the custom CSS to display the pod">
		<cfset var css = ""/>
		
		<cfsavecontent variable="css"><cfoutput>
			<style type="text/css">
			/*
			 * Styles for Last.fm plugin
			 */
			#getSetting("css")#
			</style>
		</cfoutput></cfsavecontent>
		
		<cfreturn css />
	</cffunction>
	
</cfcomponent>